/******************************************************************************* * (c) Copyright 2016 Hewlett-Packard Development Company, L.P. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License v2.0 which accompany this distribution. * * The Apache License is available at * http://www.apache.org/licenses/LICENSE-2.0 * *******************************************************************************/ package io.cloudslang.lang.cli.utils; import com.google.common.collect.Lists; import configuration.SlangEntitiesSpringConfig; import io.cloudslang.lang.api.Slang; import io.cloudslang.lang.cli.services.ConsolePrinter; import io.cloudslang.lang.commons.services.api.CompilationHelper; import io.cloudslang.lang.commons.services.api.SlangCompilationService; import io.cloudslang.lang.commons.services.api.SlangSourceService; import io.cloudslang.lang.commons.services.impl.SlangCompilationServiceImpl; import io.cloudslang.lang.commons.services.impl.SlangSourceServiceImpl; import io.cloudslang.lang.compiler.PrecompileStrategy; import io.cloudslang.lang.compiler.SlangSource; import io.cloudslang.lang.entities.SystemProperty; import io.cloudslang.lang.entities.bindings.values.ValueFactory; import io.cloudslang.lang.entities.encryption.DummyEncryptor; import java.io.File; import java.io.Serializable; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.fusesource.jansi.Ansi; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.introspector.BeanAccess; import static com.google.common.collect.Sets.newHashSet; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {CompilerHelperTest.Config.class, SlangEntitiesSpringConfig.class}) public class CompilerHelperTest { private static final String APP_HOME = "app.home"; @Autowired private CompilerHelper compilerHelper; @Autowired private Slang slang; @Autowired private ConsolePrinter consolePrinter; @Rule public ExpectedException expectedException = ExpectedException.none(); @Test(expected = NullPointerException.class) public void testFilePathWrong() throws Exception { compilerHelper.compile(null, null); } @Test(expected = IllegalArgumentException.class) public void testFilePathNotFile() throws Exception { compilerHelper.compile("xxx", null); } @Before public void resetMocks() { reset(slang); } @Test public void testUnsupportedExtension() throws Exception { final URI flowFilePath = getClass().getResource("/flow.yaml").toURI(); expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("must have one of the following extensions"); compilerHelper.compile(flowFilePath.getPath(), null); } @Test public void testDependenciesFileParentFolder() throws Exception { final URI flowPath = getClass().getResource("/executables/dir3/flow.sl").toURI(); final URI opPath = getClass().getResource("/executables/dir3/dir3_1/test_op.sl").toURI(); compilerHelper.compile(flowPath.getPath(), null); InOrder inOrder = inOrder(slang); inOrder.verify(slang).compile( SlangSource.fromFile(flowPath), newHashSet( SlangSource.fromFile(opPath), SlangSource.fromFile(flowPath) ) ); inOrder.verifyNoMoreInteractions(); } @Test public void testCompileFoldersCleanup() throws Exception { final URI folderPath = getClass().getResource("/executables/dir3").toURI(); List<String> folders = new ArrayList<>(); folders.add(folderPath.getPath()); compilerHelper.compileFolders(folders); final URI flowPath = getClass().getResource("/executables/dir3/flow.sl").toURI(); final URI opPath = getClass().getResource("/executables/dir3/dir3_1/test_op.sl").toURI(); verify(slang).compileSource( SlangSource.fromFile(opPath), newHashSet( SlangSource.fromFile(opPath), SlangSource.fromFile(flowPath) ), PrecompileStrategy.WITH_CACHE ); InOrder inOrderConsolePrinter = inOrder(consolePrinter); inOrderConsolePrinter.verify(consolePrinter, times(2)).printWithColor(any(Ansi.Color.class), anyString()); inOrderConsolePrinter.verify(consolePrinter).waitForAllPrintTasksToFinish(); inOrderConsolePrinter.verifyNoMoreInteractions(); InOrder inOrder = inOrder(slang); inOrder.verify(slang, atLeastOnce()).compileSource( SlangSource.fromFile(flowPath), newHashSet( SlangSource.fromFile(opPath), SlangSource.fromFile(flowPath) ), PrecompileStrategy.WITH_CACHE ); inOrder.verify(slang).invalidateAllInPreCompileCache(); inOrder.verifyNoMoreInteractions(); } @Test public void testFilePathValidWithOtherPathForDependencies() throws Exception { final URI flowFilePath = getClass().getResource("/flow.sl").toURI(); final URI folderPath = getClass().getResource("/executables/dir1/").toURI(); final URI flow2FilePath = getClass().getResource("/executables/dir1/flow2.sl").toURI(); compilerHelper.compile(flowFilePath.getPath(), Lists.newArrayList(folderPath.getPath())); InOrder inOrder = inOrder(slang); inOrder.verify(slang).compile(SlangSource.fromFile(flowFilePath), newHashSet(SlangSource.fromFile(flow2FilePath))); inOrder.verifyNoMoreInteractions(); } @Test public void testCompileMixedSlangFiles() throws Exception { final URI flowFilePath = getClass().getResource("/flow.sl").toURI(); final URI folderPath = getClass().getResource("/mixed_sl_files/").toURI(); final URI dependency1 = getClass() .getResource("/mixed_sl_files/configuration/properties/executables/test_flow.sl").toURI(); final URI dependency2 = getClass() .getResource("/mixed_sl_files/configuration/properties/executables/test_op.sl").toURI(); compilerHelper.compile(flowFilePath.getPath(), Lists.newArrayList(folderPath.getPath())); InOrder inOrder = inOrder(slang); inOrder.verify(slang).compile( SlangSource.fromFile(flowFilePath), newHashSet( SlangSource.fromFile(dependency1), SlangSource.fromFile(dependency2) ) ); inOrder.verifyNoMoreInteractions(); } // flowprop.sl is not recognized as properties file @Test public void testCompileDependencyPropPartOfFileName() throws Exception { final URI flowFilePath = getClass() .getResource("/flow.sl").toURI(); final URI folderPath = getClass() .getResource("/executables/dir2/").toURI(); final URI flow2FilePath = getClass() .getResource("/executables/dir2/flowprop.sl").toURI(); compilerHelper.compile(flowFilePath.getPath(), Lists.newArrayList(folderPath.getPath())); InOrder inOrder = inOrder(slang); inOrder.verify(slang).compile( SlangSource.fromFile(flowFilePath), newHashSet(SlangSource.fromFile(flow2FilePath)) ); inOrder.verifyNoMoreInteractions(); } @Test public void testInvalidDirPathForDependencies() throws Exception { final String flowFilePath = getClass().getResource("/flow.sl").getPath(); String currentDirPath = getClass().getResource("").getPath(); final String invalidDirPath = currentDirPath.concat("xxx"); expectedException.expect(RuntimeException.class); expectedException.expectMessage("xxx"); expectedException.expectMessage(SlangCompilationService.INVALID_DIRECTORY_ERROR_MESSAGE_SUFFIX); compilerHelper.compile(flowFilePath, Lists.newArrayList(invalidDirPath)); } @Test public void testInvalidDirPathForDependencies2() throws Exception { final String flowFilePath = getClass().getResource("/flow.sl").getPath(); expectedException.expect(RuntimeException.class); expectedException.expectMessage("flow.sl"); expectedException.expectMessage(SlangCompilationService.INVALID_DIRECTORY_ERROR_MESSAGE_SUFFIX); compilerHelper.compile(flowFilePath, Lists.newArrayList(flowFilePath)); } @Test public void testLoadSystemProperties() throws Exception { final Set<SystemProperty> systemProperties = newHashSet(new SystemProperty("user.sys", "props.host", "localhost"), new SystemProperty("user.sys", "props.port", "22"), new SystemProperty("user.sys", "props.alla", "balla")); final URI systemPropertyUri = getClass().getResource("/properties/system_properties.prop.sl").toURI(); final SlangSource source = SlangSource.fromFile(systemPropertyUri); when(slang.loadSystemProperties(eq(source))).thenReturn(systemProperties); compilerHelper.loadSystemProperties(Collections.singletonList(systemPropertyUri.getPath())); verify(slang).loadSystemProperties(eq(source)); } @Test public void testLoadSystemPropertiesInvalidExtension() throws Exception { final URI props1 = getClass().getResource("/properties/duplicate/props1.prop.sl").toURI(); final URI props2 = getClass().getResource("/properties/duplicate/props2.prop.sl").toURI(); Set<SystemProperty> systemProperties1 = newHashSet(new SystemProperty("user.sys", "props.host", "localhost")); Set<SystemProperty> systemProperties2 = newHashSet(new SystemProperty("user.SYS", "props.host", "localhost")); when(slang.loadSystemProperties(eq(SlangSource.fromFile(props1)))).thenReturn(systemProperties1); when(slang.loadSystemProperties(eq(SlangSource.fromFile(props2)))).thenReturn(systemProperties2); expectedException.expect(RuntimeException.class); expectedException.expectMessage(containsIgnoreCase("user.SYS.props.host")); expectedException .expectMessage("properties" + File.separator + "duplicate" + File.separator + "props1.prop.sl"); expectedException .expectMessage("properties" + File.separator + "duplicate" + File.separator + "props1.prop.sl"); compilerHelper.loadSystemProperties(Lists.newArrayList(props1.getPath(), props2.getPath())); } @Test public void testLoadSystemPropertiesDefaultFolder() throws Exception { final String initialValue = System.getProperty(APP_HOME, ""); String defaultDirPath = getClass().getResource("/mixed_sl_files/").getPath(); System.setProperty(APP_HOME, defaultDirPath); compilerHelper.loadSystemProperties(Collections.<String>emptyList()); ArgumentCaptor<SlangSource> sourceCaptor = ArgumentCaptor.forClass(SlangSource.class); verify(slang, times((2))).loadSystemProperties(sourceCaptor.capture()); Set<SlangSource> capturedSources = new HashSet<>(sourceCaptor.getAllValues()); Set<SlangSource> expectedSources = newHashSet( SlangSource.fromFile(getClass() .getResource("/mixed_sl_files/configuration/properties/properties/ubuntu.prop.sl").toURI()), SlangSource.fromFile(getClass() .getResource("/mixed_sl_files/configuration/properties/properties/windows.prop.sl").toURI()) ); Assert.assertEquals(expectedSources, capturedSources); System.setProperty(APP_HOME, initialValue); } @Test public void testLoadInputsFromFile() throws Exception { Map<String, Serializable> expected = new HashMap<>(); expected.put("host", ValueFactory.create("localhost", false)); expected.put("port", ValueFactory.create("22", false)); expected.put("username", ValueFactory.create("myusername", false)); expected.put("password", ValueFactory.create("mypassword", true)); final URI inputsFromFile = getClass().getResource("/inputs/inputs.yaml").toURI(); Map<String, ? extends Serializable> result = compilerHelper.loadInputsFromFile(Collections.singletonList(inputsFromFile.getPath())); Assert.assertNotNull(result); Assert.assertEquals(expected, result); } @Test public void testLoadInputsFromFileBadValueKey() throws Exception { expectedException.expect(RuntimeException.class); expectedException.expectMessage("inputs_value_missing.yaml} " + "has unrecognized tag {bad_value_key}. Please take a look at the supported features per versions link"); final URI inputsFromFile = getClass().getResource("/inputs/inputs_value_missing.yaml").toURI(); compilerHelper.loadInputsFromFile(Collections.singletonList(inputsFromFile.getPath())); } @Test public void testLoadInputsFromCommentedFile() throws Exception { expectedException.expect(RuntimeException.class); expectedException.expectMessage("Inputs file"); final URI inputsFromFile = getClass().getResource("/inputs/commented_inputs.yaml").toURI(); compilerHelper.loadInputsFromFile(Collections.singletonList(inputsFromFile.getPath())); } @Test public void testLoadInputsFromEmptyFile() throws Exception { expectedException.expect(RuntimeException.class); expectedException.expectMessage("Inputs file"); URI inputsFromFile = getClass().getResource("/inputs/empty_inputs.yaml").toURI(); compilerHelper.loadInputsFromFile(Collections.singletonList(inputsFromFile.getPath())); } @Ignore("Awaiting CloudSlang/cloud-slang#302 decision") @Test public void testLoadInputsFromFileImplicit() throws Exception { Map<String, ? extends Serializable> result = compilerHelper.loadInputsFromFile(null); Assert.assertNotNull(result); Assert.assertEquals(2, result.size()); } @Configuration static class Config { @Bean public Slang slang() { return mock(Slang.class); } @Bean public CompilerHelper compilerHelper() { return new CompilerHelperImpl(); } @Bean public DummyEncryptor dummyEncryptor() { return new DummyEncryptor(); } @Bean public Yaml yaml() { Yaml yaml = new Yaml(); yaml.setBeanAccess(BeanAccess.FIELD); return yaml; } @Bean public SlangSourceService slangSourceService() { return new SlangSourceServiceImpl(); } @Bean public ConsolePrinter consolePrinter() { return mock(ConsolePrinter.class); } @Bean public CompilationHelper compilationHelper() { return new ConsoleOpCompilationHelper(); } @Bean public SlangCompilationService slangCompilationService() { return new SlangCompilationServiceImpl(); } } private Matcher<String> containsIgnoreCase(final String element) { return new BaseMatcher<String>() { @Override public boolean matches(Object item) { String itemAsString = (String) item; return itemAsString.toLowerCase().contains(element.toLowerCase()); } @Override public void describeTo(Description description) { description.appendText("should contain (ignoring case) " + element); } }; } }